AWS LambdaでLambda Layersを使う場合、環境変数のPYTHONPATHは使っちゃダメという話
サーバーレス開発部@大阪の岩田です。 先日遭遇したAWS Lambdaの一風変わったエラーについてご紹介します。
結論
まず結論から。
LambdaのランタイムにPythonを選択し、かつLambda Layersの機能を利用する場合Lambdaの環境変数にPYTHONPATH
を設定しないで下さい。
レイヤー内に配置したPythonのモジュールが読み込めなくなります。
処理概要と発生したエラー
今回エラーが発生したコードです。
import os import sys import Crypto import json def lambda_handler(event, context): ...略
マネジメントコンソールからテストを実行するとこんなエラーが発生しました。
{ "errorMessage": "Unable to import module 'lambda_function'" }
どうもLambdaの初期化処理の中でCryptoモジュールが読み込めていないようです。 Cryptoモジュールはレイヤーの中に詰め込んでおり、レイヤーとLambda Functionの紐付けも正しく行えています。
なぜエラーが・・・??
原因切り分け
ここからエラーの原因を切り分けていきます。
レイヤーの作成ミスを疑う
まずレイヤーの作成ミスを疑いました。 原因切り分けのために新しく1からLambda Functionを作成し、同じレイヤーを紐付けてCryptoモジュールをimportしましたが、こちらは問題ありませんでした。 レイヤーではなくLambda Functionの方に問題がありそうです。
PYTHONPATHを疑う
Lambda Functionの設定を隅々まで見渡したところモジュールのimportに影響しそうな設定として、環境変数PYTHONPATH
が指定されているのを発見しました。
試しに環境変数PYTHONPATH
を削除してからテストイベントを実行すると、モジュールのimportエラーが解消されました!!
元々PYTHONPATH
には/var/task/vendor:/var/runtime
が指定されていたのですが、なぜこれがレイヤーに影響を??
PYTHONPATHの確認
Lambda実行環境に自動で設定されるPYTHONPATH
の値を自分で上書いているのが原因と考えました。本来はPYTHONPATH
が/var/runtime:/opt/python:/opt/python/lib/python3.6/site-packages
のように設定されるべきところLambda Functionの設定で明示的に指定したことでPYTHONPATH
から
- /opt/python
- /opt/python/lib/python3.6/site-packages
が欠落したのでは??という仮説です。
確認のためにLambda Functionの設定から環境変数PYTHONPATH
を削除した状態だとPYTHONPATH
の値がどうなるのか確認してみます。
import os import sys print(os.environ['PYTHONPATH']) import Crypto import json def lambda_handler(event, context): ...略
環境変数PYTHONPATH
の中身を出力するコードを追加しています。このコードを実行したところ、環境変数PYTHONPATH
には/var/runtime
が設定されていました。正常に実行できるパターンでもPYTHONPATH
にレイヤーのパスが設定されている訳ではないようです。
環境変数PYTHONPATHの有無によるsys.pathの比較
このままだと何故環境変数PYTHONPATH
を削除することでエラーが解消したのか分かりません。
もう少し詳細を調査してみます。
import os import sys print(sys.path) import Crypto import json def lambda_handler(event, context): ...略
Cryptoモジュールをimportする直前にprintでsys.path
の中身を出力するコードを追加しました。
上記のコードを実行し、環境変数PYTHONPATH
ありの場合と無しの場合それぞれでsys.path
の
中身を比較してみます。
PYTHONPATH無し
まずはエラーが発生しないPYTHONPATH無しバージョンです。
['/var/task', '/opt/python/lib/python3.6/site-packages', '/opt/python', '/var/runtime', '/var/runtime/awslambda', '/var/lang/lib/python36.zip', '/var/lang/lib/python3.6', '/var/lang/lib/python3.6/lib-dynload', '/var/lang/lib/python3.6/site-packages', '/opt/python/lib/python3.6/site-packages']
PYTHONPATH有り
次にエラーが発生したPYTHONPATHありバージョンのログです。
['/var/task', '/var/runtime/awslambda', '/var/runtime', '/var/lang/lib/python36.zip', '/var/lang/lib/python3.6', '/var/lang/lib/python3.6/lib-dynload', '/var/lang/lib/python3.6/site-packages']
sys.path
から
- /opt/python/lib/python3.6/site-packages
- /opt/python
が消えてしまいました!!
考察
これまでの調査結果から推測するとPythonで書いたLambda Functionの初期化処理は、環境変数PYTHONPATH
がセットされていない場合に限りsys.path
のモジュール検索パスに/opt
以下のパスを追加するようです。この結果レイヤーに配置したモジュールがLambda Functionから読み込めるようになるのです。
と、いうわけで、、、
Lambda Layersの機能を使う場合Lambda Functionの環境変数にPYTHONPATH
を設定するのはやめましょう!!
まとめ
Lambda Layers利用時の一風変わったエラーについてご紹介しました。
なぜ環境変数PYTHONPATH
をセットするとsys.path
のモジュール検索パスに/opt/
以下のパスが追加されないのでしょうか?
興味のある方はLambda実行環境をハックしてLambdaのbootstrap処理を追いかけてみると面白いかもしれません。
Lambda実行環境をハックする方法は自己責任の元Googleさんにお尋ね下さい。